#pragma once
#include <memory>
#include <unordered_map>
#include "Epoller.hpp"
#include "Connection.hpp"

//明确今天要实现的是一个怎么样的服务器！
//是用epoll来实现的，ET模式多路转接方案！


class Reactor{
private:
    //这里就没必要搞扩容机制了！
    const static int revs_num = 128;

    //单次循环等待epoll(这里封装一下，看着逻辑清晰一点)
    int LoopOnce(int timeout) {return _epoller_ptr->WaitEvent(_revs_arr, revs_num, timeout);}
    //查找辅助函数
    bool FindConnectionHelper(int sockfd) {return (_connections.find(sockfd) != _connections.end());}
    //正向查找
    bool IsConnectionExist(int fd) {return FindConnectionHelper(fd);}
    //逆向查找
    bool IsConnectionExist(std::shared_ptr<Connection> conn) {return FindConnectionHelper(conn->GetSockFd());}
public:
    //把连接加入到内核中和底层的哈希中
    void AddConnection(std::shared_ptr<Connection>& conn){
        //1.如果该连接已经在哈希表中(也就是在内核中) -> 就不进行插入
        if(IsConnectionExist(conn)) 
        {
            LOG(LogLevel::WARNING) << "conn is exists: " << conn->GetSockFd();
            return;
        }

        //2.先把该连接加入到内核
        _epoller_ptr->AddEvent(conn->GetSockFd(), conn->GetEvents());

        //3.再把当前的连接加入到哈希 因为后序需要通过哈希索引，找到对应封装好的连接进行操作！
        _connections[conn->GetSockFd()] = conn;

        //！！！4.因为连接类可需要通过通过回调指针来调用Reactor的接口，所以，加入关心列表的时候，设置一下回调指针
        conn->SetReactBackPtr(this);
    }

    void EnableReadWrite(int sockfd, bool isread, bool iswrite){
        //1.判断是否是不存在，不存在就报错了
        if(!IsConnectionExist(sockfd)) {
            LOG(LogLevel::WARNING) << "the " << sockfd << " conn is not exist";
            return;
        }

        //2.把事件进行修改，其实就是修改哈希表
        uint32_t new_event = EPOLLET | (isread == true) ? EPOLLIN : 0 | (iswrite== true) ? EPOLLOUT : 0; 
        _connections[sockfd]->SetEvents(new_event);

        //3.还需要epoll内对应的fd对应的事件修改
        _epoller_ptr->ModEvent(sockfd, new_event);
    }

    void DelConnection(int sockfd){
        //这里是把连接进行删除
        if(!IsConnectionExist(sockfd)) return;

        //注意不要先close再去删除，要不然文件描述符非法，到时候没办法在内核中进行操作的！
        std::cout << "删除 " << sockfd << std::endl;
        //1.从哈希表中删除
        _connections.erase(sockfd);
        //2.内核中也要删除
        _epoller_ptr->DelEvent(sockfd);
        //3.关闭文件描述符
        close(sockfd);
    }
public:
    Reactor()
        :_epoller_ptr(std::make_unique<Epoller>()),
        _is_running(false)
    {}

    ~Reactor(){}

    void Stop(){_is_running = false;}

    void Loop(int timeout = -1){
        if(_is_running) return;
        _is_running = true;
        while(_is_running){
            PrintFd();
            //1.今天因为是使用epoll来进行多路转接！所以需要让epoll去等待就绪事件！
            int n = LoopOnce(timeout);
            if(n < 0) continue;
            else if(n == 0) continue;
            //等待事件就绪后，执行后序操作！ 此时的n就是就绪事件的个数！
            else Disptcher(n);
        }
        Stop();
    }
private:
    void Disptcher(int num){
        //遍历就绪事件数组，一一处理事件！
        for(int i = 0; i < num; ++i){
            int fd = _revs_arr[i].data.fd;        //就绪的fd
            uint32_t revent = _revs_arr[i].events; //就绪事件

            //此时就需要根据不同的事件来进行不同的操作！
            //但是，今天我们不想单纯的处理读写事件，还有其它的一些错误的事件！
            //处理方法是这样的：我们把错误事件转化为读写事件！ -> 到时候进行读写IO操作的时候，也是会出错的！
            //然后统一在IO错误那里处理异常Excepter！

            //方法就是对于异常的错误，直接添加读写事件，让它转到读写那里去处理！
            if(revent & EPOLLERR) revent |= (EPOLLIN | EPOLLOUT);
            if(revent & EPOLLHUP) revent |= (EPOLLIN | EPOLLOUT);

            //读写这里，不使用else if了！  因为怕会有同时处理读写的情况(虽然很少)
            //还有就是，读取事件的事情，交给封装好的fd来做，也就是connection

            //读事件就绪 -> 通过fd从_connections中找到对应的封装的fd去操作即可！(可能是listener，也可能是channel)
            if(revent & EPOLLIN){
                if(IsConnectionExist(fd)) _connections[fd]->Recver();
            }
            //写事件就绪
            if(revent & EPOLLOUT){
                if(IsConnectionExist(fd)) _connections[fd]->Sender();
            }


            //Tips:因为这里对于异常的处理，也给规划到IO错误了！（这是不想在这个逻辑中写过多的if else去判断！）
            //但是，一旦异常处理了，对应的fd就需要从红黑树中(_epoller_ptr)移除关心，而且也会从_connections中移除
            //所以，如果被移除了，就不能再进行后序得IO操作了！
        }
    }

    void PrintFd(){
        std::cout << "当前关心的文件描述符: ";
        for(auto& it : _connections){
            std::cout << it.second->GetSockFd() << " ";
        }
        std::cout << std::endl;
    }

private:
    //1.需要一个epoll模型，为了解耦，这里需要做一下封装
    std::unique_ptr<Epoller> _epoller_ptr;

    //2.今天来说，我们想把 链接管理 交给一个专门的模块Listener来做
    //但是，对于监听套接字和普通套接字，本质上都是处理一样的事件，还是有一些共通的 -> 搞一个继承体系出来！
    //而且，该服务器内必然有多个不同状态的、不同需求的fd，所以需要管理！
    std::unordered_map<int, std::shared_ptr<Connection>> _connections;

    //3.该服务器的状态！
    bool _is_running;

    //4.等待就绪事件的数组，epoll_wait返回的！
    epoll_event _revs_arr[revs_num];
};